package main

import (
	"context"
	"fmt"
	"net"
	"os"
	"os/signal"
	"runtime"
	"strings"
	"syscall"
	"time"

	"github.com/alecthomas/log4go"
	"pac/conf"
	"pac/db"
	"pac/device"
	"pac/sm"
	"pac/user"
)

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	log4go.Info("GOMAXPROCS: %v", runtime.NumCPU())

	log4go.Info("pac run with command: %v", strings.Join(os.Args[:], " "))

	defer func() {
		log4go.Error("pac quit")
		log4go.Close()
		os.Exit(1)
	}()

	if len(os.Args) < 2 {
		log4go.Error("wrong input args !")
		return
	}

	if err := conf.InitConf(os.Args[1]); err != nil {
		log4go.Error("InitConf failure: %v", err)
		return
	}

	log4go.LoadConfiguration(conf.Log.ConfFile)

	sig := make(chan os.Signal, 1)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
	go func() {
		sig := <-sig
		log4go.Error("pac quit because received signal: %v", sig)
		time.Sleep(1 * time.Second)
		log4go.Close()
		os.Exit(0)
	}()

	service()
}

func service() {
	err := database.InitDB(conf.DB.DataSourceName)
	if err != nil {
		log4go.Error("failed to initial database: %v, quit!", err)
		return
	}

	sm.SmartMeter(true)

	ctx := context.Background()

	go listen(ctx, conf.DeviceSrv.Bind, conf.DeviceSrv.Port, device.HandleConn)
	go listen(ctx, conf.DeviceModbusSrv.Bind, conf.DeviceModbusSrv.Port, sm.HandleConn)
	listen(ctx, conf.UserSrv.Bind, conf.UserSrv.Port, user.HandleConn)
}

func listen(ctx context.Context, bind string, port int, handler func(conn net.Conn)) {
	var err error
	var bindAddr string
	var netListen net.Listener

	for {
		bindAddr = fmt.Sprintf("%s:%d", bind, port)
		netListen, err = net.Listen("tcp", bindAddr)
		if err != nil {
			log4go.Error("listen on %v failure: %v", bindAddr, err.Error())
			time.Sleep(time.Second)
		} else {
			log4go.Info("listen on %v", bindAddr)
			break
		}
	}

	defer netListen.Close()

	log4go.Info("waiting for clients")

	var tempDelay time.Duration
	for {
		conn, err := netListen.Accept()
		if err != nil {
			if netErr, ok := err.(net.Error); ok && netErr.Temporary() {
				if tempDelay == 0 {
					tempDelay = 5 * time.Millisecond
				} else {
					tempDelay *= 2
				}
				if max := 1 * time.Second; tempDelay > max {
					tempDelay = max
				}
				log4go.Info("Accept error: %v; retrying in %v", err, tempDelay)
				time.Sleep(tempDelay)
				continue
			}
			log4go.Error(err)
			return
		}
		tempDelay = 0

		go handler(conn)
	}
}
